1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 package com.sun.jmx.mbeanserver;
27
28 import static com.sun.jmx.mbeanserver.Util.*;
29 import static com.sun.jmx.mbeanserver.MXBeanIntrospector.typeName;
30
31 import static javax.management.openmbean.SimpleType.*;
32
33 import com.sun.jmx.remote.util.EnvHelp;
34
35 import java.beans.ConstructorProperties;
36 import java.io.InvalidObjectException;
37 import java.lang.annotation.ElementType;
38 import java.lang.ref.WeakReference;
39 import java.lang.reflect.Array;
40 import java.lang.reflect.Constructor;
41 import java.lang.reflect.Field;
42 import java.lang.reflect.GenericArrayType;
43 import java.lang.reflect.Method;
44 import java.lang.reflect.Modifier;
45 import java.lang.reflect.ParameterizedType;
46 import java.lang.reflect.Proxy;
47 import java.lang.reflect.Type;
48 import java.util.ArrayList;
49 import java.util.Arrays;
50 import java.util.BitSet;
51 import java.util.Collection;
52 import java.util.Comparator;
53 import java.util.HashSet;
54 import java.util.List;
55 import java.util.Map;
56 import java.util.Set;
57 import java.util.SortedMap;
58 import java.util.SortedSet;
59 import java.util.TreeSet;
60 import java.util.WeakHashMap;
61
62 import javax.management.JMX;
63 import javax.management.ObjectName;
64 import javax.management.openmbean.ArrayType;
65 import javax.management.openmbean.CompositeData;
66 import javax.management.openmbean.CompositeDataInvocationHandler;
67 import javax.management.openmbean.CompositeDataSupport;
68 import javax.management.openmbean.CompositeDataView;
69 import javax.management.openmbean.CompositeType;
70 import javax.management.openmbean.OpenDataException;
71 import javax.management.openmbean.OpenType;
72 import javax.management.openmbean.SimpleType;
73 import javax.management.openmbean.TabularData;
74 import javax.management.openmbean.TabularDataSupport;
75 import javax.management.openmbean.TabularType;
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118 public class DefaultMXBeanMappingFactory extends MXBeanMappingFactory {
119 static abstract class NonNullMXBeanMapping extends MXBeanMapping {
120 NonNullMXBeanMapping(Type javaType, OpenType<?> openType) {
121 super(javaType, openType);
122 }
123
124 @Override
125 public final Object fromOpenValue(Object openValue)
126 throws InvalidObjectException {
127 if (openValue == null)
128 return null;
129 else
130 return fromNonNullOpenValue(openValue);
131 }
132
133 @Override
134 public final Object toOpenValue(Object javaValue) throws OpenDataException {
135 if (javaValue == null)
136 return null;
137 else
138 return toNonNullOpenValue(javaValue);
139 }
140
141 abstract Object fromNonNullOpenValue(Object openValue)
142 throws InvalidObjectException;
143
144 abstract Object toNonNullOpenValue(Object javaValue)
145 throws OpenDataException;
146
147
148
149
150
151 boolean isIdentity() {
152 return false;
153 }
154 }
155
156 static boolean isIdentity(MXBeanMapping mapping) {
157 return (mapping instanceof NonNullMXBeanMapping &&
158 ((NonNullMXBeanMapping) mapping).isIdentity());
159 }
160
161 private static final class Mappings
162 extends WeakHashMap<Type, WeakReference<MXBeanMapping>> {}
163
164 private static final Mappings mappings = new Mappings();
165
166
167
168 private static final List<MXBeanMapping> permanentMappings = newList();
169
170 private static synchronized MXBeanMapping getMapping(Type type) {
171 WeakReference<MXBeanMapping> wr = mappings.get(type);
172 return (wr == null) ? null : wr.get();
173 }
174
175 private static synchronized void putMapping(Type type, MXBeanMapping mapping) {
176 WeakReference<MXBeanMapping> wr =
177 new WeakReference<MXBeanMapping>(mapping);
178 mappings.put(type, wr);
179 }
180
181 private static synchronized void putPermanentMapping(
182 Type type, MXBeanMapping mapping) {
183 putMapping(type, mapping);
184 permanentMappings.add(mapping);
185 }
186
187 static {
188
189
190 final OpenType<?>[] simpleTypes = {
191 BIGDECIMAL, BIGINTEGER, BOOLEAN, BYTE, CHARACTER, DATE,
192 DOUBLE, FLOAT, INTEGER, LONG, OBJECTNAME, SHORT, STRING,
193 VOID,
194 };
195
196 for (int i = 0; i < simpleTypes.length; i++) {
197 final OpenType<?> t = simpleTypes[i];
198 Class<?> c;
199 try {
200 c = Class.forName(t.getClassName(), false,
201 ObjectName.class.getClassLoader());
202 } catch (ClassNotFoundException e) {
203
204 throw new Error(e);
205 }
206 final MXBeanMapping mapping = new IdentityMapping(c, t);
207 putPermanentMapping(c, mapping);
208
209 if (c.getName().startsWith("java.lang.")) {
210 try {
211 final Field typeField = c.getField("TYPE");
212 final Class<?> primitiveType = (Class<?>) typeField.get(null);
213 final MXBeanMapping primitiveMapping =
214 new IdentityMapping(primitiveType, t);
215 putPermanentMapping(primitiveType, primitiveMapping);
216 if (primitiveType != void.class) {
217 final Class<?> primitiveArrayType =
218 Array.newInstance(primitiveType, 0).getClass();
219 final OpenType<?> primitiveArrayOpenType =
220 ArrayType.getPrimitiveArrayType(primitiveArrayType);
221 final MXBeanMapping primitiveArrayMapping =
222 new IdentityMapping(primitiveArrayType,
223 primitiveArrayOpenType);
224 putPermanentMapping(primitiveArrayType,
225 primitiveArrayMapping);
226 }
227 } catch (NoSuchFieldException e) {
228
229 } catch (IllegalAccessException e) {
230
231 assert(false);
232 }
233 }
234 }
235 }
236
237
238 @Override
239 public synchronized MXBeanMapping mappingForType(Type objType,
240 MXBeanMappingFactory factory)
241 throws OpenDataException {
242 if (inProgress.containsKey(objType)) {
243 throw new OpenDataException(
244 "Recursive data structure, including " + typeName(objType));
245 }
246
247 MXBeanMapping mapping;
248
249 mapping = getMapping(objType);
250 if (mapping != null)
251 return mapping;
252
253 inProgress.put(objType, objType);
254 try {
255 mapping = makeMapping(objType, factory);
256 } catch (OpenDataException e) {
257 throw openDataException("Cannot convert type: " + typeName(objType), e);
258 } finally {
259 inProgress.remove(objType);
260 }
261
262 putMapping(objType, mapping);
263 return mapping;
264 }
265
266 private MXBeanMapping makeMapping(Type objType, MXBeanMappingFactory factory)
267 throws OpenDataException {
268
269
270
271
272 if (objType instanceof GenericArrayType) {
273 Type componentType =
274 ((GenericArrayType) objType).getGenericComponentType();
275 return makeArrayOrCollectionMapping(objType, componentType, factory);
276 } else if (objType instanceof Class<?>) {
277 Class<?> objClass = (Class<?>) objType;
278 if (objClass.isEnum()) {
279
280
281
282 return makeEnumMapping((Class<?>) objClass, ElementType.class);
283 } else if (objClass.isArray()) {
284 Type componentType = objClass.getComponentType();
285 return makeArrayOrCollectionMapping(objClass, componentType,
286 factory);
287 } else if (JMX.isMXBeanInterface(objClass)) {
288 return makeMXBeanRefMapping(objClass);
289 } else {
290 return makeCompositeMapping(objClass, factory);
291 }
292 } else if (objType instanceof ParameterizedType) {
293 return makeParameterizedTypeMapping((ParameterizedType) objType,
294 factory);
295 } else
296 throw new OpenDataException("Cannot map type: " + objType);
297 }
298
299 private static <T extends Enum<T>> MXBeanMapping
300 makeEnumMapping(Class<?> enumClass, Class<T> fake) {
301 return new EnumMapping<T>(Util.<Class<T>>cast(enumClass));
302 }
303
304
305
306
307
308
309 private MXBeanMapping
310 makeArrayOrCollectionMapping(Type collectionType, Type elementType,
311 MXBeanMappingFactory factory)
312 throws OpenDataException {
313
314 final MXBeanMapping elementMapping = factory.mappingForType(elementType, factory);
315 final OpenType<?> elementOpenType = elementMapping.getOpenType();
316 final ArrayType<?> openType = ArrayType.getArrayType(elementOpenType);
317 final Class<?> elementOpenClass = elementMapping.getOpenClass();
318
319 final Class<?> openArrayClass;
320 final String openArrayClassName;
321 if (elementOpenClass.isArray())
322 openArrayClassName = "[" + elementOpenClass.getName();
323 else
324 openArrayClassName = "[L" + elementOpenClass.getName() + ";";
325 try {
326 openArrayClass = Class.forName(openArrayClassName);
327 } catch (ClassNotFoundException e) {
328 throw openDataException("Cannot obtain array class", e);
329 }
330
331 if (collectionType instanceof ParameterizedType) {
332 return new CollectionMapping(collectionType,
333 openType, openArrayClass,
334 elementMapping);
335 } else {
336 if (isIdentity(elementMapping)) {
337 return new IdentityMapping(collectionType,
338 openType);
339 } else {
340 return new ArrayMapping(collectionType,
341 openType,
342 openArrayClass,
343 elementMapping);
344 }
345 }
346 }
347
348 private static final String[] keyArray = {"key"};
349 private static final String[] keyValueArray = {"key", "value"};
350
351 private MXBeanMapping
352 makeTabularMapping(Type objType, boolean sortedMap,
353 Type keyType, Type valueType,
354 MXBeanMappingFactory factory)
355 throws OpenDataException {
356
357 final String objTypeName = typeName(objType);
358 final MXBeanMapping keyMapping = factory.mappingForType(keyType, factory);
359 final MXBeanMapping valueMapping = factory.mappingForType(valueType, factory);
360 final OpenType<?> keyOpenType = keyMapping.getOpenType();
361 final OpenType<?> valueOpenType = valueMapping.getOpenType();
362 final CompositeType rowType =
363 new CompositeType(objTypeName,
364 objTypeName,
365 keyValueArray,
366 keyValueArray,
367 new OpenType<?>[] {keyOpenType, valueOpenType});
368 final TabularType tabularType =
369 new TabularType(objTypeName, objTypeName, rowType, keyArray);
370 return new TabularMapping(objType, sortedMap, tabularType,
371 keyMapping, valueMapping);
372 }
373
374
375
376
377
378
379 private MXBeanMapping
380 makeParameterizedTypeMapping(ParameterizedType objType,
381 MXBeanMappingFactory factory)
382 throws OpenDataException {
383
384 final Type rawType = objType.getRawType();
385
386 if (rawType instanceof Class<?>) {
387 Class<?> c = (Class<?>) rawType;
388 if (c == List.class || c == Set.class || c == SortedSet.class) {
389 Type[] actuals = objType.getActualTypeArguments();
390 assert(actuals.length == 1);
391 if (c == SortedSet.class)
392 mustBeComparable(c, actuals[0]);
393 return makeArrayOrCollectionMapping(objType, actuals[0], factory);
394 } else {
395 boolean sortedMap = (c == SortedMap.class);
396 if (c == Map.class || sortedMap) {
397 Type[] actuals = objType.getActualTypeArguments();
398 assert(actuals.length == 2);
399 if (sortedMap)
400 mustBeComparable(c, actuals[0]);
401 return makeTabularMapping(objType, sortedMap,
402 actuals[0], actuals[1], factory);
403 }
404 }
405 }
406 throw new OpenDataException("Cannot convert type: " + objType);
407 }
408
409 private static MXBeanMapping makeMXBeanRefMapping(Type t)
410 throws OpenDataException {
411 return new MXBeanRefMapping(t);
412 }
413
414 private MXBeanMapping makeCompositeMapping(Class<?> c,
415 MXBeanMappingFactory factory)
416 throws OpenDataException {
417
418
419
420
421 final boolean gcInfoHack =
422 (c.getName().equals("com.sun.management.GcInfo") &&
423 c.getClassLoader() == null);
424
425 final List<Method> methods =
426 MBeanAnalyzer.eliminateCovariantMethods(Arrays.asList(c.getMethods()));
427 final SortedMap<String,Method> getterMap = newSortedMap();
428
429
430
431
432 for (Method method : methods) {
433 final String propertyName = propertyName(method);
434
435 if (propertyName == null)
436 continue;
437 if (gcInfoHack && propertyName.equals("CompositeType"))
438 continue;
439
440 Method old =
441 getterMap.put(decapitalize(propertyName),
442 method);
443 if (old != null) {
444 final String msg =
445 "Class " + c.getName() + " has method name clash: " +
446 old.getName() + ", " + method.getName();
447 throw new OpenDataException(msg);
448 }
449 }
450
451 final int nitems = getterMap.size();
452
453 if (nitems == 0) {
454 throw new OpenDataException("Can't map " + c.getName() +
455 " to an open data type");
456 }
457
458 final Method[] getters = new Method[nitems];
459 final String[] itemNames = new String[nitems];
460 final OpenType<?>[] openTypes = new OpenType<?>[nitems];
461 int i = 0;
462 for (Map.Entry<String,Method> entry : getterMap.entrySet()) {
463 itemNames[i] = entry.getKey();
464 final Method getter = entry.getValue();
465 getters[i] = getter;
466 final Type retType = getter.getGenericReturnType();
467 openTypes[i] = factory.mappingForType(retType, factory).getOpenType();
468 i++;
469 }
470
471 CompositeType compositeType =
472 new CompositeType(c.getName(),
473 c.getName(),
474 itemNames,
475 itemNames,
476 openTypes);
477
478 return new CompositeMapping(c,
479 compositeType,
480 itemNames,
481 getters,
482 factory);
483 }
484
485
486
487
488
489
490
491 private static final class IdentityMapping extends NonNullMXBeanMapping {
492 IdentityMapping(Type targetType, OpenType<?> openType) {
493 super(targetType, openType);
494 }
495
496 boolean isIdentity() {
497 return true;
498 }
499
500 @Override
501 Object fromNonNullOpenValue(Object openValue)
502 throws InvalidObjectException {
503 return openValue;
504 }
505
506 @Override
507 Object toNonNullOpenValue(Object javaValue) throws OpenDataException {
508 return javaValue;
509 }
510 }
511
512 private static final class EnumMapping<T extends Enum<T>>
513 extends NonNullMXBeanMapping {
514
515 EnumMapping(Class<T> enumClass) {
516 super(enumClass, SimpleType.STRING);
517 this.enumClass = enumClass;
518 }
519
520 @Override
521 final Object toNonNullOpenValue(Object value) {
522 return ((Enum<?>) value).name();
523 }
524
525 @Override
526 final T fromNonNullOpenValue(Object value)
527 throws InvalidObjectException {
528 try {
529 return Enum.valueOf(enumClass, (String) value);
530 } catch (Exception e) {
531 throw invalidObjectException("Cannot convert to enum: " +
532 value, e);
533 }
534 }
535
536 private final Class<T> enumClass;
537 }
538
539 private static final class ArrayMapping extends NonNullMXBeanMapping {
540 ArrayMapping(Type targetType,
541 ArrayType<?> openArrayType, Class<?> openArrayClass,
542 MXBeanMapping elementMapping) {
543 super(targetType, openArrayType);
544 this.elementMapping = elementMapping;
545 }
546
547 @Override
548 final Object toNonNullOpenValue(Object value)
549 throws OpenDataException {
550 Object[] valueArray = (Object[]) value;
551 final int len = valueArray.length;
552 final Object[] openArray = (Object[])
553 Array.newInstance(getOpenClass().getComponentType(), len);
554 for (int i = 0; i < len; i++)
555 openArray[i] = elementMapping.toOpenValue(valueArray[i]);
556 return openArray;
557 }
558
559 @Override
560 final Object fromNonNullOpenValue(Object openValue)
561 throws InvalidObjectException {
562 final Object[] openArray = (Object[]) openValue;
563 final Type javaType = getJavaType();
564 final Object[] valueArray;
565 final Type componentType;
566 if (javaType instanceof GenericArrayType) {
567 componentType =
568 ((GenericArrayType) javaType).getGenericComponentType();
569 } else if (javaType instanceof Class<?> &&
570 ((Class<?>) javaType).isArray()) {
571 componentType = ((Class<?>) javaType).getComponentType();
572 } else {
573 throw new IllegalArgumentException("Not an array: " +
574 javaType);
575 }
576 valueArray = (Object[]) Array.newInstance((Class<?>) componentType,
577 openArray.length);
578 for (int i = 0; i < openArray.length; i++)
579 valueArray[i] = elementMapping.fromOpenValue(openArray[i]);
580 return valueArray;
581 }
582
583 public void checkReconstructible() throws InvalidObjectException {
584 elementMapping.checkReconstructible();
585 }
586
587
588
589
590
591
592 private final MXBeanMapping elementMapping;
593 }
594
595 private static final class CollectionMapping extends NonNullMXBeanMapping {
596 CollectionMapping(Type targetType,
597 ArrayType<?> openArrayType,
598 Class<?> openArrayClass,
599 MXBeanMapping elementMapping) {
600 super(targetType, openArrayType);
601 this.elementMapping = elementMapping;
602
603
604
605
606
607 Type raw = ((ParameterizedType) targetType).getRawType();
608 Class<?> c = (Class<?>) raw;
609 final Class<?> collC;
610 if (c == List.class)
611 collC = ArrayList.class;
612 else if (c == Set.class)
613 collC = HashSet.class;
614 else if (c == SortedSet.class)
615 collC = TreeSet.class;
616 else {
617 assert(false);
618 collC = null;
619 }
620 collectionClass = Util.cast(collC);
621 }
622
623 @Override
624 final Object toNonNullOpenValue(Object value)
625 throws OpenDataException {
626 final Collection<?> valueCollection = (Collection<?>) value;
627 if (valueCollection instanceof SortedSet<?>) {
628 Comparator<?> comparator =
629 ((SortedSet<?>) valueCollection).comparator();
630 if (comparator != null) {
631 final String msg =
632 "Cannot convert SortedSet with non-null comparator: " +
633 comparator;
634 throw openDataException(msg, new IllegalArgumentException(msg));
635 }
636 }
637 final Object[] openArray = (Object[])
638 Array.newInstance(getOpenClass().getComponentType(),
639 valueCollection.size());
640 int i = 0;
641 for (Object o : valueCollection)
642 openArray[i++] = elementMapping.toOpenValue(o);
643 return openArray;
644 }
645
646 @Override
647 final Object fromNonNullOpenValue(Object openValue)
648 throws InvalidObjectException {
649 final Object[] openArray = (Object[]) openValue;
650 final Collection<Object> valueCollection;
651 try {
652 valueCollection = cast(collectionClass.newInstance());
653 } catch (Exception e) {
654 throw invalidObjectException("Cannot create collection", e);
655 }
656 for (Object o : openArray) {
657 Object value = elementMapping.fromOpenValue(o);
658 if (!valueCollection.add(value)) {
659 final String msg =
660 "Could not add " + o + " to " +
661 collectionClass.getName() +
662 " (duplicate set element?)";
663 throw new InvalidObjectException(msg);
664 }
665 }
666 return valueCollection;
667 }
668
669 public void checkReconstructible() throws InvalidObjectException {
670 elementMapping.checkReconstructible();
671 }
672
673 private final Class<? extends Collection<?>> collectionClass;
674 private final MXBeanMapping elementMapping;
675 }
676
677 private static final class MXBeanRefMapping extends NonNullMXBeanMapping {
678 MXBeanRefMapping(Type intf) {
679 super(intf, SimpleType.OBJECTNAME);
680 }
681
682 @Override
683 final Object toNonNullOpenValue(Object javaValue)
684 throws OpenDataException {
685 MXBeanLookup lookup = lookupNotNull(OpenDataException.class);
686 ObjectName name = lookup.mxbeanToObjectName(javaValue);
687 if (name == null)
688 throw new OpenDataException("No name for object: " + javaValue);
689 return name;
690 }
691
692 @Override
693 final Object fromNonNullOpenValue(Object openValue)
694 throws InvalidObjectException {
695 MXBeanLookup lookup = lookupNotNull(InvalidObjectException.class);
696 ObjectName name = (ObjectName) openValue;
697 Object mxbean =
698 lookup.objectNameToMXBean(name, (Class<?>) getJavaType());
699 if (mxbean == null) {
700 final String msg =
701 "No MXBean for name: " + name;
702 throw new InvalidObjectException(msg);
703 }
704 return mxbean;
705 }
706
707 private <T extends Exception> MXBeanLookup
708 lookupNotNull(Class<T> excClass)
709 throws T {
710 MXBeanLookup lookup = MXBeanLookup.getLookup();
711 if (lookup == null) {
712 final String msg =
713 "Cannot convert MXBean interface in this context";
714 T exc;
715 try {
716 Constructor<T> con = excClass.getConstructor(String.class);
717 exc = con.newInstance(msg);
718 } catch (Exception e) {
719 throw new RuntimeException(e);
720 }
721 throw exc;
722 }
723 return lookup;
724 }
725 }
726
727 private static final class TabularMapping extends NonNullMXBeanMapping {
728 TabularMapping(Type targetType,
729 boolean sortedMap,
730 TabularType tabularType,
731 MXBeanMapping keyConverter,
732 MXBeanMapping valueConverter) {
733 super(targetType, tabularType);
734 this.sortedMap = sortedMap;
735 this.keyMapping = keyConverter;
736 this.valueMapping = valueConverter;
737 }
738
739 @Override
740 final Object toNonNullOpenValue(Object value) throws OpenDataException {
741 final Map<Object, Object> valueMap = cast(value);
742 if (valueMap instanceof SortedMap<?,?>) {
743 Comparator<?> comparator = ((SortedMap<?,?>) valueMap).comparator();
744 if (comparator != null) {
745 final String msg =
746 "Cannot convert SortedMap with non-null comparator: " +
747 comparator;
748 throw openDataException(msg, new IllegalArgumentException(msg));
749 }
750 }
751 final TabularType tabularType = (TabularType) getOpenType();
752 final TabularData table = new TabularDataSupport(tabularType);
753 final CompositeType rowType = tabularType.getRowType();
754 for (Map.Entry<Object, Object> entry : valueMap.entrySet()) {
755 final Object openKey = keyMapping.toOpenValue(entry.getKey());
756 final Object openValue = valueMapping.toOpenValue(entry.getValue());
757 final CompositeData row;
758 row =
759 new CompositeDataSupport(rowType, keyValueArray,
760 new Object[] {openKey,
761 openValue});
762 table.put(row);
763 }
764 return table;
765 }
766
767 @Override
768 final Object fromNonNullOpenValue(Object openValue)
769 throws InvalidObjectException {
770 final TabularData table = (TabularData) openValue;
771 final Collection<CompositeData> rows = cast(table.values());
772 final Map<Object, Object> valueMap =
773 sortedMap ? newSortedMap() : newInsertionOrderMap();
774 for (CompositeData row : rows) {
775 final Object key =
776 keyMapping.fromOpenValue(row.get("key"));
777 final Object value =
778 valueMapping.fromOpenValue(row.get("value"));
779 if (valueMap.put(key, value) != null) {
780 final String msg =
781 "Duplicate entry in TabularData: key=" + key;
782 throw new InvalidObjectException(msg);
783 }
784 }
785 return valueMap;
786 }
787
788 @Override
789 public void checkReconstructible() throws InvalidObjectException {
790 keyMapping.checkReconstructible();
791 valueMapping.checkReconstructible();
792 }
793
794 private final boolean sortedMap;
795 private final MXBeanMapping keyMapping;
796 private final MXBeanMapping valueMapping;
797 }
798
799 private final class CompositeMapping extends NonNullMXBeanMapping {
800 CompositeMapping(Class<?> targetClass,
801 CompositeType compositeType,
802 String[] itemNames,
803 Method[] getters,
804 MXBeanMappingFactory factory) throws OpenDataException {
805 super(targetClass, compositeType);
806
807 assert(itemNames.length == getters.length);
808
809 this.itemNames = itemNames;
810 this.getters = getters;
811 this.getterMappings = new MXBeanMapping[getters.length];
812 for (int i = 0; i < getters.length; i++) {
813 Type retType = getters[i].getGenericReturnType();
814 getterMappings[i] = factory.mappingForType(retType, factory);
815 }
816 }
817
818 @Override
819 final Object toNonNullOpenValue(Object value)
820 throws OpenDataException {
821 CompositeType ct = (CompositeType) getOpenType();
822 if (value instanceof CompositeDataView)
823 return ((CompositeDataView) value).toCompositeData(ct);
824 if (value == null)
825 return null;
826
827 Object[] values = new Object[getters.length];
828 for (int i = 0; i < getters.length; i++) {
829 try {
830 Object got = getters[i].invoke(value, (Object[]) null);
831 values[i] = getterMappings[i].toOpenValue(got);
832 } catch (Exception e) {
833 throw openDataException("Error calling getter for " +
834 itemNames[i] + ": " + e, e);
835 }
836 }
837 return new CompositeDataSupport(ct, itemNames, values);
838 }
839
840
841
842
843
844 private synchronized void makeCompositeBuilder()
845 throws InvalidObjectException {
846 if (compositeBuilder != null)
847 return;
848
849 Class<?> targetClass = (Class<?>) getJavaType();
850
851
852
853 CompositeBuilder[][] builders = {
854 {
855 new CompositeBuilderViaFrom(targetClass, itemNames),
856 },
857 {
858 new CompositeBuilderViaConstructor(targetClass, itemNames),
859 },
860 {
861 new CompositeBuilderCheckGetters(targetClass, itemNames,
862 getterMappings),
863 new CompositeBuilderViaSetters(targetClass, itemNames),
864 new CompositeBuilderViaProxy(targetClass, itemNames),
865 },
866 };
867 CompositeBuilder foundBuilder = null;
868
869
870
871 final StringBuilder whyNots = new StringBuilder();
872 Throwable possibleCause = null;
873 find:
874 for (CompositeBuilder[] relatedBuilders : builders) {
875 for (int i = 0; i < relatedBuilders.length; i++) {
876 CompositeBuilder builder = relatedBuilders[i];
877 String whyNot = builder.applicable(getters);
878 if (whyNot == null) {
879 foundBuilder = builder;
880 break find;
881 }
882 Throwable cause = builder.possibleCause();
883 if (cause != null)
884 possibleCause = cause;
885 if (whyNot.length() > 0) {
886 if (whyNots.length() > 0)
887 whyNots.append("; ");
888 whyNots.append(whyNot);
889 if (i == 0)
890 break;
891 }
892 }
893 }
894 if (foundBuilder == null) {
895 String msg =
896 "Do not know how to make a " + targetClass.getName() +
897 " from a CompositeData: " + whyNots;
898 if (possibleCause != null)
899 msg += ". Remaining exceptions show a POSSIBLE cause.";
900 throw invalidObjectException(msg, possibleCause);
901 }
902 compositeBuilder = foundBuilder;
903 }
904
905 @Override
906 public void checkReconstructible() throws InvalidObjectException {
907 makeCompositeBuilder();
908 }
909
910 @Override
911 final Object fromNonNullOpenValue(Object value)
912 throws InvalidObjectException {
913 makeCompositeBuilder();
914 return compositeBuilder.fromCompositeData((CompositeData) value,
915 itemNames,
916 getterMappings);
917 }
918
919 private final String[] itemNames;
920 private final Method[] getters;
921 private final MXBeanMapping[] getterMappings;
922 private CompositeBuilder compositeBuilder;
923 }
924
925
926 private static abstract class CompositeBuilder {
927 CompositeBuilder(Class<?> targetClass, String[] itemNames) {
928 this.targetClass = targetClass;
929 this.itemNames = itemNames;
930 }
931
932 Class<?> getTargetClass() {
933 return targetClass;
934 }
935
936 String[] getItemNames() {
937 return itemNames;
938 }
939
940
941
942
943
944
945 abstract String applicable(Method[] getters)
946 throws InvalidObjectException;
947
948
949
950
951
952
953
954 Throwable possibleCause() {
955 return null;
956 }
957
958 abstract Object fromCompositeData(CompositeData cd,
959 String[] itemNames,
960 MXBeanMapping[] converters)
961 throws InvalidObjectException;
962
963 private final Class<?> targetClass;
964 private final String[] itemNames;
965 }
966
967
968
969 private static final class CompositeBuilderViaFrom
970 extends CompositeBuilder {
971
972 CompositeBuilderViaFrom(Class<?> targetClass, String[] itemNames) {
973 super(targetClass, itemNames);
974 }
975
976 String applicable(Method[] getters) throws InvalidObjectException {
977
978
979 Class<?> targetClass = getTargetClass();
980 try {
981 Method fromMethod =
982 targetClass.getMethod("from", CompositeData.class);
983
984 if (!Modifier.isStatic(fromMethod.getModifiers())) {
985 final String msg =
986 "Method from(CompositeData) is not static";
987 throw new InvalidObjectException(msg);
988 }
989
990 if (fromMethod.getReturnType() != getTargetClass()) {
991 final String msg =
992 "Method from(CompositeData) returns " +
993 typeName(fromMethod.getReturnType()) +
994 " not " + typeName(targetClass);
995 throw new InvalidObjectException(msg);
996 }
997
998 this.fromMethod = fromMethod;
999 return null;
1000 } catch (InvalidObjectException e) {
1001 throw e;
1002 } catch (Exception e) {
1003
1004 return "no method from(CompositeData)";
1005 }
1006 }
1007
1008 final Object fromCompositeData(CompositeData cd,
1009 String[] itemNames,
1010 MXBeanMapping[] converters)
1011 throws InvalidObjectException {
1012 try {
1013 return fromMethod.invoke(null, cd);
1014 } catch (Exception e) {
1015 final String msg = "Failed to invoke from(CompositeData)";
1016 throw invalidObjectException(msg, e);
1017 }
1018 }
1019
1020 private Method fromMethod;
1021 }
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033 private static class CompositeBuilderCheckGetters extends CompositeBuilder {
1034 CompositeBuilderCheckGetters(Class<?> targetClass, String[] itemNames,
1035 MXBeanMapping[] getterConverters) {
1036 super(targetClass, itemNames);
1037 this.getterConverters = getterConverters;
1038 }
1039
1040 String applicable(Method[] getters) {
1041 for (int i = 0; i < getters.length; i++) {
1042 try {
1043 getterConverters[i].checkReconstructible();
1044 } catch (InvalidObjectException e) {
1045 possibleCause = e;
1046 return "method " + getters[i].getName() + " returns type " +
1047 "that cannot be mapped back from OpenData";
1048 }
1049 }
1050 return "";
1051 }
1052
1053 @Override
1054 Throwable possibleCause() {
1055 return possibleCause;
1056 }
1057
1058 final Object fromCompositeData(CompositeData cd,
1059 String[] itemNames,
1060 MXBeanMapping[] converters) {
1061 throw new Error();
1062 }
1063
1064 private final MXBeanMapping[] getterConverters;
1065 private Throwable possibleCause;
1066 }
1067
1068
1069 private static class CompositeBuilderViaSetters extends CompositeBuilder {
1070
1071 CompositeBuilderViaSetters(Class<?> targetClass, String[] itemNames) {
1072 super(targetClass, itemNames);
1073 }
1074
1075 String applicable(Method[] getters) {
1076 try {
1077 Constructor<?> c = getTargetClass().getConstructor();
1078 } catch (Exception e) {
1079 return "does not have a public no-arg constructor";
1080 }
1081
1082 Method[] setters = new Method[getters.length];
1083 for (int i = 0; i < getters.length; i++) {
1084 Method getter = getters[i];
1085 Class<?> returnType = getter.getReturnType();
1086 String name = propertyName(getter);
1087 String setterName = "set" + name;
1088 Method setter;
1089 try {
1090 setter = getTargetClass().getMethod(setterName, returnType);
1091 if (setter.getReturnType() != void.class)
1092 throw new Exception();
1093 } catch (Exception e) {
1094 return "not all getters have corresponding setters " +
1095 "(" + getter + ")";
1096 }
1097 setters[i] = setter;
1098 }
1099 this.setters = setters;
1100 return null;
1101 }
1102
1103 Object fromCompositeData(CompositeData cd,
1104 String[] itemNames,
1105 MXBeanMapping[] converters)
1106 throws InvalidObjectException {
1107 Object o;
1108 try {
1109 o = getTargetClass().newInstance();
1110 for (int i = 0; i < itemNames.length; i++) {
1111 if (cd.containsKey(itemNames[i])) {
1112 Object openItem = cd.get(itemNames[i]);
1113 Object javaItem =
1114 converters[i].fromOpenValue(openItem);
1115 setters[i].invoke(o, javaItem);
1116 }
1117 }
1118 } catch (Exception e) {
1119 throw invalidObjectException(e);
1120 }
1121 return o;
1122 }
1123
1124 private Method[] setters;
1125 }
1126
1127
1128
1129
1130 private static final class CompositeBuilderViaConstructor
1131 extends CompositeBuilder {
1132
1133 CompositeBuilderViaConstructor(Class<?> targetClass, String[] itemNames) {
1134 super(targetClass, itemNames);
1135 }
1136
1137 String applicable(Method[] getters) throws InvalidObjectException {
1138
1139 final Class<ConstructorProperties> propertyNamesClass = ConstructorProperties.class;
1140
1141 Class<?> targetClass = getTargetClass();
1142 Constructor<?>[] constrs = targetClass.getConstructors();
1143
1144
1145 List<Constructor<?>> annotatedConstrList = newList();
1146 for (Constructor<?> constr : constrs) {
1147 if (Modifier.isPublic(constr.getModifiers())
1148 && constr.getAnnotation(propertyNamesClass) != null)
1149 annotatedConstrList.add(constr);
1150 }
1151
1152 if (annotatedConstrList.isEmpty())
1153 return "no constructor has @ConstructorProperties annotation";
1154
1155 annotatedConstructors = newList();
1156
1157
1158
1159
1160
1161 Map<String, Integer> getterMap = newMap();
1162 String[] itemNames = getItemNames();
1163 for (int i = 0; i < itemNames.length; i++)
1164 getterMap.put(itemNames[i], i);
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175 Set<BitSet> getterIndexSets = newSet();
1176 for (Constructor<?> constr : annotatedConstrList) {
1177 String[] propertyNames =
1178 constr.getAnnotation(propertyNamesClass).value();
1179
1180 Type[] paramTypes = constr.getGenericParameterTypes();
1181 if (paramTypes.length != propertyNames.length) {
1182 final String msg =
1183 "Number of constructor params does not match " +
1184 "@ConstructorProperties annotation: " + constr;
1185 throw new InvalidObjectException(msg);
1186 }
1187
1188 int[] paramIndexes = new int[getters.length];
1189 for (int i = 0; i < getters.length; i++)
1190 paramIndexes[i] = -1;
1191 BitSet present = new BitSet();
1192
1193 for (int i = 0; i < propertyNames.length; i++) {
1194 String propertyName = propertyNames[i];
1195 if (!getterMap.containsKey(propertyName)) {
1196 String msg =
1197 "@ConstructorProperties includes name " + propertyName +
1198 " which does not correspond to a property";
1199 for (String getterName : getterMap.keySet()) {
1200 if (getterName.equalsIgnoreCase(propertyName)) {
1201 msg += " (differs only in case from property " +
1202 getterName + ")";
1203 }
1204 }
1205 msg += ": " + constr;
1206 throw new InvalidObjectException(msg);
1207 }
1208 int getterIndex = getterMap.get(propertyName);
1209 paramIndexes[getterIndex] = i;
1210 if (present.get(getterIndex)) {
1211 final String msg =
1212 "@ConstructorProperties contains property " +
1213 propertyName + " more than once: " + constr;
1214 throw new InvalidObjectException(msg);
1215 }
1216 present.set(getterIndex);
1217 Method getter = getters[getterIndex];
1218 Type propertyType = getter.getGenericReturnType();
1219 if (!propertyType.equals(paramTypes[i])) {
1220 final String msg =
1221 "@ConstructorProperties gives property " + propertyName +
1222 " of type " + propertyType + " for parameter " +
1223 " of type " + paramTypes[i] + ": " + constr;
1224 throw new InvalidObjectException(msg);
1225 }
1226 }
1227
1228 if (!getterIndexSets.add(present)) {
1229 final String msg =
1230 "More than one constructor has a @ConstructorProperties " +
1231 "annotation with this set of names: " +
1232 Arrays.toString(propertyNames);
1233 throw new InvalidObjectException(msg);
1234 }
1235
1236 Constr c = new Constr(constr, paramIndexes, present);
1237 annotatedConstructors.add(c);
1238 }
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255 for (BitSet a : getterIndexSets) {
1256 boolean seen = false;
1257 for (BitSet b : getterIndexSets) {
1258 if (a == b)
1259 seen = true;
1260 else if (seen) {
1261 BitSet u = new BitSet();
1262 u.or(a); u.or(b);
1263 if (!getterIndexSets.contains(u)) {
1264 Set<String> names = new TreeSet<String>();
1265 for (int i = u.nextSetBit(0); i >= 0;
1266 i = u.nextSetBit(i+1))
1267 names.add(itemNames[i]);
1268 final String msg =
1269 "Constructors with @ConstructorProperties annotation " +
1270 " would be ambiguous for these items: " +
1271 names;
1272 throw new InvalidObjectException(msg);
1273 }
1274 }
1275 }
1276 }
1277
1278 return null;
1279 }
1280
1281 final Object fromCompositeData(CompositeData cd,
1282 String[] itemNames,
1283 MXBeanMapping[] mappings)
1284 throws InvalidObjectException {
1285
1286
1287
1288
1289
1290 CompositeType ct = cd.getCompositeType();
1291 BitSet present = new BitSet();
1292 for (int i = 0; i < itemNames.length; i++) {
1293 if (ct.getType(itemNames[i]) != null)
1294 present.set(i);
1295 }
1296
1297 Constr max = null;
1298 for (Constr constr : annotatedConstructors) {
1299 if (subset(constr.presentParams, present) &&
1300 (max == null ||
1301 subset(max.presentParams, constr.presentParams)))
1302 max = constr;
1303 }
1304
1305 if (max == null) {
1306 final String msg =
1307 "No constructor has a @ConstructorProperties for this set of " +
1308 "items: " + ct.keySet();
1309 throw new InvalidObjectException(msg);
1310 }
1311
1312 Object[] params = new Object[max.presentParams.cardinality()];
1313 for (int i = 0; i < itemNames.length; i++) {
1314 if (!max.presentParams.get(i))
1315 continue;
1316 Object openItem = cd.get(itemNames[i]);
1317 Object javaItem = mappings[i].fromOpenValue(openItem);
1318 int index = max.paramIndexes[i];
1319 if (index >= 0)
1320 params[index] = javaItem;
1321 }
1322
1323 try {
1324 return max.constructor.newInstance(params);
1325 } catch (Exception e) {
1326 final String msg =
1327 "Exception constructing " + getTargetClass().getName();
1328 throw invalidObjectException(msg, e);
1329 }
1330 }
1331
1332 private static boolean subset(BitSet sub, BitSet sup) {
1333 BitSet subcopy = (BitSet) sub.clone();
1334 subcopy.andNot(sup);
1335 return subcopy.isEmpty();
1336 }
1337
1338 private static class Constr {
1339 final Constructor<?> constructor;
1340 final int[] paramIndexes;
1341 final BitSet presentParams;
1342 Constr(Constructor<?> constructor, int[] paramIndexes,
1343 BitSet presentParams) {
1344 this.constructor = constructor;
1345 this.paramIndexes = paramIndexes;
1346 this.presentParams = presentParams;
1347 }
1348 }
1349
1350 private List<Constr> annotatedConstructors;
1351 }
1352
1353
1354
1355
1356
1357 private static final class CompositeBuilderViaProxy
1358 extends CompositeBuilder {
1359
1360 CompositeBuilderViaProxy(Class<?> targetClass, String[] itemNames) {
1361 super(targetClass, itemNames);
1362 }
1363
1364 String applicable(Method[] getters) {
1365 Class<?> targetClass = getTargetClass();
1366 if (!targetClass.isInterface())
1367 return "not an interface";
1368 Set<Method> methods =
1369 newSet(Arrays.asList(targetClass.getMethods()));
1370 methods.removeAll(Arrays.asList(getters));
1371
1372
1373
1374 String bad = null;
1375 for (Method m : methods) {
1376 String mname = m.getName();
1377 Class<?>[] mparams = m.getParameterTypes();
1378 try {
1379 Method om = Object.class.getMethod(mname, mparams);
1380 if (!Modifier.isPublic(om.getModifiers()))
1381 bad = mname;
1382 } catch (NoSuchMethodException e) {
1383 bad = mname;
1384 }
1385
1386
1387
1388
1389 }
1390 if (bad != null)
1391 return "contains methods other than getters (" + bad + ")";
1392 return null;
1393 }
1394
1395 final Object fromCompositeData(CompositeData cd,
1396 String[] itemNames,
1397 MXBeanMapping[] converters) {
1398 final Class<?> targetClass = getTargetClass();
1399 return
1400 Proxy.newProxyInstance(targetClass.getClassLoader(),
1401 new Class<?>[] {targetClass},
1402 new CompositeDataInvocationHandler(cd));
1403 }
1404 }
1405
1406 static InvalidObjectException invalidObjectException(String msg,
1407 Throwable cause) {
1408 return EnvHelp.initCause(new InvalidObjectException(msg), cause);
1409 }
1410
1411 static InvalidObjectException invalidObjectException(Throwable cause) {
1412 return invalidObjectException(cause.getMessage(), cause);
1413 }
1414
1415 static OpenDataException openDataException(String msg, Throwable cause) {
1416 return EnvHelp.initCause(new OpenDataException(msg), cause);
1417 }
1418
1419 static OpenDataException openDataException(Throwable cause) {
1420 return openDataException(cause.getMessage(), cause);
1421 }
1422
1423 static void mustBeComparable(Class<?> collection, Type element)
1424 throws OpenDataException {
1425 if (!(element instanceof Class<?>)
1426 || !Comparable.class.isAssignableFrom((Class<?>) element)) {
1427 final String msg =
1428 "Parameter class " + element + " of " +
1429 collection.getName() + " does not implement " +
1430 Comparable.class.getName();
1431 throw new OpenDataException(msg);
1432 }
1433 }
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448 public static String decapitalize(String name) {
1449 if (name == null || name.length() == 0) {
1450 return name;
1451 }
1452 int offset1 = Character.offsetByCodePoints(name, 0, 1);
1453
1454 if (offset1 < name.length() &&
1455 Character.isUpperCase(name.codePointAt(offset1)))
1456 return name;
1457 return name.substring(0, offset1).toLowerCase() +
1458 name.substring(offset1);
1459 }
1460
1461
1462
1463
1464
1465
1466
1467 static String capitalize(String name) {
1468 if (name == null || name.length() == 0)
1469 return name;
1470 int offset1 = name.offsetByCodePoints(0, 1);
1471 return name.substring(0, offset1).toUpperCase() +
1472 name.substring(offset1);
1473 }
1474
1475 public static String propertyName(Method m) {
1476 String rest = null;
1477 String name = m.getName();
1478 if (name.startsWith("get"))
1479 rest = name.substring(3);
1480 else if (name.startsWith("is") && m.getReturnType() == boolean.class)
1481 rest = name.substring(2);
1482 if (rest == null || rest.length() == 0
1483 || m.getParameterTypes().length > 0
1484 || m.getReturnType() == void.class
1485 || name.equals("getClass"))
1486 return null;
1487 return rest;
1488 }
1489
1490 private final static Map<Type, Type> inProgress = newIdentityHashMap();
1491
1492 }